/*
 * Albareth - an action roleplaying game.
 * Copyright (C) 2008 Jens Anuth
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 *   02110-1301, USA.
 * 
 * $Id: Action.java 44 2008-05-14 18:22:13Z jens464 $
*/
package albareth.core.action;

import albareth.core.Game;
import albareth.core.entity.Mobile;
import albareth.util.Location;
import albareth.util.MobileTask;

public abstract class Action extends MobileTask implements Cloneable
{
    public static final int NEEDS_LOS       = 0x2;
    public static final int HARMFUL         = 0x4;
    public static final int TARGET_LOC      = 0x8;
    public static final int TARGET_MOB      = 0x10;
    public static final int TARGET_NOSELF   = 0x20;
    public static final int TARGETLOC_CLEAR = 0x40;
    public static final int TARGET_SELFONLY = 0x80;
    
    private String _name;
    
    private int _flags = 0;
    
    private int _cooldownGroup = -1;
    private int _cooldownDuration;
    private int _castingDuration;
    private int _maxRange = 1;
    
    protected Mobile internalTargetMobile = null;
    protected Location internalTargetLocation = null;
    
    private long _cooldownEndTime;
    
    public Action(String name)
    {
        _name = name;
    }
    
    public String getName()
    {
        return _name;
    }

    public void setName(String name)
    {
        _name = name;
    }

    
    /**
     * Verifies the current target.
     * 
     * @return 1 need target location, but have none.
     *         2 need target mob, but have none.
     *         3 targeted myself, but this is not allowed.
     *         4 needs LoS, but have none.
     *         5 target is out of range
     *         6 target location must be free, but is blocked
     *         0 ok. target is valid.
     */
    public int verifyTarget(Mobile src)
    {
        if (isFlagSet(TARGET_LOC))
        {
            if (internalTargetLocation == null) {
                return 1;
            }
            if (isFlagSet(TARGETLOC_CLEAR) &&
                internalTargetLocation.isBlocked())
            {
                return 6;
            }
            if (internalTargetLocation.equals(src.getLocation()) &&
                    isFlagSet(TARGET_NOSELF))
            {
                return 3;
            }
        }
        
        if (isFlagSet(TARGET_MOB))
        {
            if (internalTargetMobile == null) {
                return 2;
            }
            if (internalTargetMobile == src && isFlagSet(TARGET_NOSELF)) {
                return 3;
            }
        }
        
        if (isFlagSet(NEEDS_LOS) &&
            !src.hasLineOfSight(internalTargetLocation))
        {
            return 4;
        }
        
        if (src.getLocation().distance(internalTargetLocation) > _maxRange)
        {
            return 5;
        }
        
        return 0;
    }
    
    public boolean isTargetNeeded()
    {
        return !isFlagSet(TARGET_SELFONLY) && isFlagSet(TARGET_MOB|TARGET_LOC);
    }
    
    public void setFlags(int flags)
    {
        _flags |= flags;
    }
    
    public boolean isFlagSet(int flags)
    {
        return (_flags & flags) > 0;
    }
    
    public boolean allFlagsSet(int flags)
    {
        return (_flags & flags) == flags;
    }


    public int getCastingDelay()
    {
        return _castingDuration;
    }
    
    

    public void setCastingDuration(int castingDuration)
    {
        _castingDuration = castingDuration;
    }

    public final long getCastingDelayLeft()
    {
        return getNextExecutionTime() - Game.getContext().getPreciseTime();
    }
    
    public final long getCastingDelayElapsed()
    {
        return getCastingDelay() - getCastingDelayLeft();
    }

    public final boolean isActivated()
    {
        return isScheduled(); 
    }

    
    
    public int getCooldownDuration()
    {
        return _cooldownDuration;
    }

    public void setCooldownDuration(int cooldownDuration)
    {
        _cooldownDuration = cooldownDuration;
    }

    public final long getCooldownDurationLeft()
    {
        return _cooldownEndTime - Game.getContext().getPreciseTime();
    }
    
    public final long getCooldownDurationElapsed()
    {
        return getCooldownDuration() - getCooldownDurationLeft();
    }
    
    public final boolean isCoolingDown()
    {
        return getCooldownDurationLeft() > 0;
    }
    
    
    public int getCooldownGroup()
    {
        return _cooldownGroup;
    }

    public void setCooldownGroup(int cooldownGroup)
    {
        _cooldownGroup = cooldownGroup;
    }

    
    /*
    public final boolean isActive()
    {
        return (isCasting() || isCoolingDown());
    }
    */
    
    public boolean canActivate(Mobile user)
    {
        return true;
    }
    
    public final boolean isReady()
    {
        return (!isActivated() && !isCoolingDown());
    }

    protected boolean verifyConditions(Mobile src)
    {
        if (src.isFlagSet(Mobile.FLAG_DEAD)) {
            return false;
        }
        
        if (!isReady()) {
            return false;
        }
        
        if (isTargetNeeded())
        {
            switch (verifyTarget(src))
            {
                case 1: 
                    src.infoMsg("Point the mouse over a target and press the according action key.");
                    return false;
                case 2:
                    src.infoMsg("Must target a creature.");
                    return false;
                case 3:
                    src.infoMsg("Not on yourself!");
                    return false;
                case 4:
                    src.infoMsg("Cannot see the target.");
                    return false;
                case 5:
                    src.infoMsg("Target out of range.");
                    return false;
                case 6:
                    src.infoMsg("Target location blocked.");
                    return false;
                default: //target ok
                    return true;
            }
        }
        
        return true;
    }
    
    public boolean initiateMobileTask(Mobile src)
    {
        updateInternalTargets(src);

        if (!verifyConditions(src)) {
            return false;
        }
        
        if (onInitiate(src) == false) {
            return false;
        }

        return true;
    }

    private void updateInternalTargets(Mobile src)
    {
        if (isTargetNeeded()) {
            internalTargetMobile = src.getMobileController().getTargetMobile();
            internalTargetLocation = src.getMobileController().getTargetLocation();
        } else {
            internalTargetMobile = src;
            internalTargetLocation = src.getLocation();
        }
    }

    protected void abortMobileTask(Mobile owner)
    {
        //TODO: onAbort(Mobile src, GameContext context) if needed.
    }
    
    protected boolean executeMobileTask(Mobile src)
    {
        updateInternalTargets(src);
        
        if (!verifyConditions(src)) {
            abort();
            return false;
        }
        
        if (onExecute(src) == false) {
            return false;
        };
        
        //start cooldowns
        if (getCooldownDuration() > 0) {
            _cooldownEndTime = Game.getContext().getPreciseTime() + getCooldownDuration();
        }
        
        if (getCooldownGroup() > -1) {
            src.activateGroupCooldown(getCooldownGroup());
        }
        
        return true;
    }
    
    protected abstract boolean onInitiate(Mobile src);
    
    protected abstract boolean onExecute(Mobile src);
    
    
    public int getRange()
    {
        return _maxRange;
    }

    public void setRange(int range)
    {
        _maxRange = range;
    }

    
    public Object clone() throws CloneNotSupportedException
    {
        return super.clone();
    }

}
